import os
import urllib
import pytest
import re
from common.Load_Yaml_Util.load_yaml import load_test_cases
from common.Load_Yaml_Util.load_yaml import load_config_host
from common.requests_util import RequestUtil
from common.logger_util import LogUtils
from common.response import ResponseObject
from common.assertions import AssertionMap

# 加载全局配置文件
# config = load_config_host('../configs/config.yaml')
config = load_config_host('configs/config.yaml')

# 初始化token变量为None
token = None

# 使用环境变量获取测试用例文件名，如果未设置，则默认为 'data/test_cases.yaml'
# test_cases_file = os.getenv('TEST_CASES_FILE', '../data/xuanwu_yaml/vlladd_virtual_host/test_virtual_host_a.yaml')
test_cases_file = os.getenv('TEST_CASES_FILE', '../data/xuanwu_yaml/node/test_node_add.yaml')
# test_cases_file = os.getenv('TEST_CASES_FILE', 'data/test_logic_port.yaml')

# 字符串${} 包含的字符串
variable_pattern = re.compile(r'^\$\{(.*?)\}$')
def load_variables(variables,shared_data):
    """
    函数用于将变量加载到共享数据字典中
    Args:
        variables:
        shared_data:
    """
    for k in variables:
        shared_data[k] = variables[k]

def update_params(params, shared_data:dict):
    """替换params中的变量
        如果某个值符合variable_pattern正则  则替换
    Args:
        params (dict | list): 变更的字典或者数组
        shared_data (dict): 共享变量
    """
    if isinstance(params, dict):
        for k in params:
            if isinstance(params[k], dict) or isinstance(params[k], list):
                update_params(params[k], shared_data)
            elif isinstance(params[k],str):
                r = variable_pattern.search(params[k])
                if r:
                    key = r.group(1)
                    if key in shared_data:
                        params[k] = shared_data[key]

    elif isinstance(params, list):
        for idx in range(len(params)):
            if isinstance(params[idx], dict) or isinstance(params[idx], list):
                update_params(params[idx], shared_data)
            elif isinstance(params[idx], str):
                r = variable_pattern.search(params[idx])
                if r:
                    key = r.group(1)
                    if key in shared_data:
                        params[idx] = shared_data[key]

def update_items(items:list, shared_data:dict, resp_obj:ResponseObject):
    """替换items中的变量
        对items里面的字符串变量进行更换
        如果满足${}正则 就去全局共享变量里查找
        否则去resp_obj里找
    Args:
        items (list): 需要变换的数组
        shared_data (dict): 共享数据字典
        resp_obj (ResponseObject): 返回的结构体
    """
    for idx in range(len(items)):
        if isinstance(items[idx], str):
            r = variable_pattern.search(items[idx])
            if r:
                key = r.group(1)
                if key in shared_data:
                    items[idx] = shared_data[key]
            elif idx == 0:
                # 只对第一个变量做替换查找返回结果中的值
                # 这样就规定yaml中 -eq :[a, b] 此种格式 第一个数据可以是返回结果里面的值
                items[idx] = resp_obj._search_jmespath(items[idx])

# 加载Testsuit和测试用例
testsuit, test_cases = load_test_cases(test_cases_file)

def replace_url_params(url, shared_data):
    """  shared_data
    替换URL中的路径参数，支持从shared_data中动态映射键到URL占位符。
    """
    decoded_url = urllib.parse.unquote(url)
    # print("Decoded URL:", decoded_url)  # 调试信息
    # 查找所有占位符
    placeholders = re.findall(r'\{(.*?)\}', decoded_url)
    for placeholder in placeholders:
        # 直接替换
        if placeholder in shared_data:
            decoded_url = decoded_url.replace(f'{{{placeholder}}}', str(shared_data[placeholder]))
        else:
            # 尝试查找映射的键
            mapped_key = next((key for key in shared_data if key.startswith(placeholder)), None)
            if mapped_key:
                decoded_url = decoded_url.replace(f'{{{placeholder}}}', str(shared_data[mapped_key]))

    encoded_url = urllib.parse.quote(decoded_url, safe='/:?&=')
    # print(encoded_url,'00000000000')
    return encoded_url

class TestClass:
    # 共享变量
    shared_data = None
    
    @classmethod
    def setup_class(cls):
        # 初始化共享变量
        cls.shared_data = {}
        LogUtils.info(f"Testsuit: {testsuit}")
        
    @classmethod
    def teardown_class(cls):
        cls.shared_data = None

    # 参数化执行测试用例
    @pytest.mark.parametrize('test_case', test_cases)
    def test_run_test_case(self, test_case):
        global token  # 声明token为全局变量

        LogUtils.info('==============> 测试用例执行开始 <==============')
        try:
            # 构建完整的URL
            url = 'http://' + config['base_url'] + test_case['url']
            # 替换URL路径参数
            url = replace_url_params(url, TestClass.shared_data)
            # print(url)

            # 添加固定变量到共享变量中
            variables = test_case.get('variables',{})
            load_variables(variables,TestClass.shared_data)

            # 获取测试用例的参数
            name = test_case.get('name', None)
            method = test_case.get('method', None)
            params = test_case.get('params', None)
            print(params)

            # 替换params中的变量
            update_params(params,TestClass.shared_data)

            # # 获取查询参数
            query_params = test_case.get('query_params', None)
            # # 如果参数中包含token，替换为上一个接口返回的token值
            if query_params:
                if 'token' in query_params:
                    query_params['token'] = token
                    LogUtils.info('【登录token】：' + str(token))

            # 发送请求并获取响应
            response = RequestUtil().send_request(test_case['method'], url, params=params, query_params=query_params)
            # print(response.url)
            resp_obj = ResponseObject(response)

            # 打印日志
            LogUtils.info('【用例名称】：' + str(name))
            LogUtils.info('【接口请求地址】：' + response.url)
            LogUtils.info('【请求方式】：' + str(method))
            LogUtils.info('【接口请求参数】：' + str(params))
            LogUtils.info('【接口响应结果】：' + response.text.encode().decode("unicode_escape"))

            # 获取公共参数
            extract = test_case.get('extract',{})
            resp_obj.load_extract(extract, TestClass.shared_data)

            print('【共享变量值】___: ', TestClass.shared_data) # 调试信息

            # 保存token值
            if 'data' in response.json().keys():
                if 'token' in response.json()['data'].keys():
                    token = response.json()['data']['token']

            assertions = test_case.get('assertions', [])
            LogUtils.info(f'【断言操作】:{assertions}')
            for assertion in assertions:
                for op in assertion:
                    items = assertion[op]
                    LogUtils.info(f"【执行断言操作】: {op} {items}")
                    update_items(items, TestClass.shared_data, resp_obj)
                    if op in AssertionMap:
                        assert_fn = AssertionMap[op]
                        assert_fn(items)
                    else:
                        LogUtils.error(f"【！！！未知断言操作】: {op}")
        finally:
            LogUtils.info('==============++++++++++> 测试用例执行结束 <++++++++++==============')
            LogUtils.info('{-------}')
            LogUtils.info('{-------}')
            LogUtils.info('{-------}')



if __name__ == '__main__':
    pytest.main()


